package cz.apnetis.fitweb.client;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

import cz.apnetis.fitweb.shared.Constants;
import cz.apnetis.fitweb.shared.DayFoodKind;
import cz.apnetis.fitweb.shared.Food;
import cz.apnetis.fitweb.shared.YearPeriod;
import cz.apnetis.fitweb.shared.dto.FoodDTO;
import cz.apnetis.gwt.client.EditableLabel;
import cz.apnetis.gwt.client.Repaintable;
import cz.apnetis.gwt.client.ResultTableEditable.AbstractTableModelEditable;
import cz.apnetis.gwt.client.TableModelSum;

public class FoodTableModel extends AbstractTableModelEditable<Food> implements TableModelSum {

  private final String[] columnNames = new String[] { FoodFields.DATE.toString(),
      FoodFields.TIME.toString(), FoodFields.KIND.toString(), FoodFields.NAME.toString(),
      FoodFields.INGREDIENT.toString(), FoodFields.KJ.toString(), FoodFields.KCAL.toString() };

  private FoodServiceAsync foodService; public void setFoodService(final FoodServiceAsync foodService) { this.foodService = foodService; } public FoodServiceAsync getFoodService() { return this.foodService; }
  private int offset = 0;

  /** Map which hold KJ and KCAL label on row */
  private final Map<Integer, EditableLabel<Integer>[]> kjKcalLabel = new HashMap<Integer, EditableLabel<Integer>[]>();

  private final EditableLabel<Integer> kjSum = new EditableLabel<Integer>();
  private final EditableLabel<Integer> kcalSum = new EditableLabel<Integer>();

  public FoodTableModel() {
    super();
    for (final DayFoodKind dfk : DayFoodKind.values()) {
      addListBoxItem(FoodFields.KIND.toString(), dfk.getI18nString(), dfk.toString());
    }
    final Date now = new Date();
    final int day = now.getDay();
    now.setHours(0);
    now.setMinutes(0);
    now.setSeconds(0);
    final int quoter = 15 * Constants.MILIS_IN_MINUTE;
    while (day == now.getDay()) {
      final String value = DateTimeFormat.getShortTimeFormat().format(now);
      addListBoxItem(FoodFields.TIME.toString(), value, value);
      now.setTime(now.getTime() + quoter);
    }
  }

  @Override
  public String[] getColumnNames() { return this.columnNames; }
  @Override
  public String getColumnNameI18n(final String columnName) {
    return FoodFields.valueOf(columnName).getI18nString();
  }

  private final void calculateSum() {
    int kj = 0;
    int kcal = 0;
    for (final EditableLabel<Integer>[] ar : this.kjKcalLabel.values()) {
      if (ar[0] != null) { kj += ar[0].getValue().intValue(); }
      if (ar[1] != null) { kcal += ar[1].getValue().intValue(); }
    }
    this.kjSum.setValue(kj);
    this.kcalSum.setValue(kcal);
  }

  @Override
  public final boolean hasPrevious() { return this.offset > 0; }
  @Override
  public final boolean hasNext() { return true; }

  @Override
  public int getRowCount() { return getValues().size() + 1; }

  @Override
  public final void loadNext(final Repaintable repaintAfter) {
    this.offset++;
    loadDate(repaintAfter);
  }

  @Override
  public final boolean isSumRow(final int row) {
    return getValues().size() == row;
  }

  @Override
  public final Widget getFieldWidget(final int row, final String colName) {
    final FoodFields ff = FoodFields.valueOf(colName);
    if (ff == FoodFields.KJ || ff == FoodFields.KCAL) {
      if (isSumRow(row)) {
        calculateSum();
        if (ff == FoodFields.KJ) { return this.kjSum; }
        return this.kcalSum;
      }
      final EditableLabel<Integer> label = new EditableLabel<Integer>();
      final Food food = getValues().get(row);
      int value = food.getKj().intValue();
      if (ff == FoodFields.KCAL) { value = Math.round(Constants.JOULE_2_CALORIE * value); }
      label.setValue(Integer.valueOf(value));

      label.addValueChangeHandler(new KjKcalChangeHandler(row, ff));
      EditableLabel<Integer>[] ela = this.kjKcalLabel.get(Integer.valueOf(row));
      if (ela == null) {
        ela = new EditableLabel[2];
        this.kjKcalLabel.put(Integer.valueOf(row), ela);
      }
      if (ff == FoodFields.KJ) { ela[0] = label; }
      else { ela[1] = label; }
      return label;
    } else {
      if (isSumRow(row)) { return new Label(); }
      return super.getFieldWidget(row, colName);
    }
  }

  @Override
  public final void loadPrevious(final Repaintable repaintAfter) {
    if (this.offset > 0) { this.offset--; }
    loadDate(repaintAfter);
  }

  private final void loadDate(final Repaintable repaintAfter) {
    final Date now = new Date();
    this.foodService.foodOfPeriod(YearPeriod.DAY.ordinal(),
        this.offset, now.getTimezoneOffset(), new AsyncCallback<List<Food>>() {
      @Override
      public void onFailure(final Throwable caught) {
        // TODO Auto-generated method stub
      }
      @Override
      public final void onSuccess(final List<Food> result) {
        setValues(result);
        FoodTableModel.this.kjKcalLabel.clear();
        repaintAfter.repaint();
      }
    });
  }

  @Override
  protected Object getField(final Food value, final String colName) {
    if (value.getKj() == null) { value.setKj(Integer.valueOf(0)); }
    final FoodFields ff = FoodFields.valueOf(colName);
    switch (ff) {
      case DATE : return value.getTime();
      case TIME : return DateTimeFormat.getShortTimeFormat().format(value.getTime());
      case KIND : return value.getFoodKind();
      case NAME :
        if (value.getName() == null || "".equals(value.getName())) {
          return FitwebMessages.INSTANCE.food_def_name();
        }
        return value.getName();
      case INGREDIENT :
        if (value.getIngredientOverview() == null || "".equals(value.getIngredientOverview())) {
          return FitwebMessages.INSTANCE.food_def_ingredientOverview();
        }
        return value.getIngredientOverview();
      default : return "<Column undefined>";
    }
  }

  @Override
  protected void setField(final Food value, final String colName, final Object newFieldValue) {
    final FoodFields ff = FoodFields.valueOf(colName);
    switch (ff) {
      case DATE :
        final Date date = (Date) newFieldValue;
        final Date time = value.getTime();
        date.setHours(time.getHours());
        date.setMinutes(time.getMinutes());
        date.setSeconds(0);
        value.setTime(date);
        break;
      case TIME :
        DateTimeFormat.getShortTimeFormat().parse(String.valueOf(newFieldValue), 0, value.getTime());
        break;
      case KIND : value.setFoodKind(String.valueOf(newFieldValue)); break;
      case NAME :
        if ("".equals(newFieldValue) || FitwebMessages.INSTANCE.food_def_name().equals(newFieldValue)) {
          value.setName(null);
        } else { value.setName(String.valueOf(newFieldValue)); }
        break;
      case INGREDIENT :
        if ("".equals(newFieldValue) || FitwebMessages.INSTANCE.food_def_ingredientOverview().equals(newFieldValue)) {
          value.setIngredientOverview(null);
        } else { value.setIngredientOverview(String.valueOf(newFieldValue)); }
        break;
      case KJ :
        value.setKj((Integer) newFieldValue);
        break;
    }
  }
  @Override
  public final String getResultPosition() {
    final Date date = new Date();
    date.setTime(date.getTime() - 86400000 * this.offset);
    return DateTimeFormat.getShortDateFormat().format(date);
  }
  @Override
  public void removeRow(final int row) {
    this.foodService.remove(getValues().get(row), new AsyncCallback<String>() {
      @Override public void onFailure(final Throwable caught) { // TODO Auto-generated method stub
      }
      @Override public void onSuccess(final String newResult) { // TODO Auto-generated method stub
      }
    });
    getValues().remove(row);
  }
  @Override
  public void saveRow(final int row) {
    this.foodService.save(getValues().get(row), new AsyncCallback<String>() {
      @Override public void onFailure(final Throwable caught) { // TODO Auto-generated method stub
      }
      @Override public void onSuccess(final String newResult) { // TODO Auto-generated method stub
      }
    });
  }
  @Override
  public final int addRow() {
    getValues().add(0, newFood());
    return 0;
  }

  /** Get current time */
  private static final Date roundTimeToQuaters(final Date date) {
    date.setSeconds(0);
    if (date.getMinutes() < 15) { date.setMinutes(0); }
    else if (date.getMinutes() < 30) { date.setMinutes(15); }
    else if (date.getMinutes() < 45) { date.setMinutes(30); }
    else { date.setMinutes(45); }
    return date;
  }

  /** Return selected data with current time */
  private final Date giveSelecteDate() {
    final Date result = new Date();

    if (this.offset > 0) {
      result.setTime(result.getTime() - this.offset * Constants.MILIS_IN_DAY);
    }

    return result;
  }

  /** Method which create new food */
  public final Food newFood() {
    final FoodDTO result = new FoodDTO();
    result.setTime(roundTimeToQuaters(giveSelecteDate()));
    result.setName(FitwebMessages.INSTANCE.food_def_name());
    result.setIngredientOverview(FitwebMessages.INSTANCE.food_def_ingredientOverview());
    result.setFoodKind(DayFoodKind.kindForTime(result.getTime()).toString());
    result.setKj(Integer.valueOf(0));
    return result;
  }

  private class KjKcalChangeHandler implements ValueChangeHandler {
    private final Integer row;
    private final FoodFields ff;
    public KjKcalChangeHandler(final int row, final FoodFields ff) {
      this.row = Integer.valueOf(row);
      this.ff = ff;
    }
    @Override
    public final void onValueChange(final ValueChangeEvent event) {
      final EditableLabel<Integer>[] ela = FoodTableModel.this.kjKcalLabel.get(Integer.valueOf(this.row));
      if (this.ff == FoodFields.KJ) {
        ela[1].setValue(Math.round(ela[0].getValue().intValue() * Constants.JOULE_2_CALORIE));
      } else {
        ela[0].setValue(Math.round(ela[1].getValue().intValue() * Constants.CALORIE_2_JOULE));
      }
      calculateSum();
    }
  }
}